package bb.lanxing.mvvm.base.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import bb.lanxing.net.retrofit.GenericResponse
import bb.lanxing.net.retrofit.exception.ResponseException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.Subscription

abstract class BaseViewModel(
    application: Application,
) : AndroidViewModel(application) {
    val actionLiveData: SingleLiveEvent<ContextAction> = SingleLiveEvent()
    val subscriptions: ArrayList<Subscription> = ArrayList()
    val tipsMutableLiveData: SingleLiveEvent<TipAction> = SingleLiveEvent()

    override fun onCleared() {
        super.onCleared()
        val it: Iterator<Subscription> = subscriptions.iterator()
        while (it.hasNext()) {
            val next = it.next()
            next.unsubscribe()
        }
    }

    fun executeMain(block: suspend CoroutineScope.() -> Unit): Job {
        return viewModelScope.launch(Dispatchers.Main) {
            block()
        }
    }

    fun executeIO(block: suspend CoroutineScope.() -> Unit): Job {
        return viewModelScope.launch {
            withContext(Dispatchers.IO) {
                block()
            }
        }
    }

    fun execute(
        block: suspend CoroutineScope.() -> Unit,
        error: suspend CoroutineScope.(r: ResponseException) -> Unit,
        complete: suspend CoroutineScope.() -> Unit,
        showLoading: Boolean
    ): Job {
        if (showLoading) {
            actionLiveData.postValue(ContextAction(ContextAction.ACTION_SHOW_LOADING))
        }
        return executeMain {
            handleException({
                withContext(Dispatchers.IO) {
                    block()
                }
            }, {
                error(it)
            }, {
                if (showLoading) {
                    actionLiveData.postValue(ContextAction(ContextAction.ACTION_HIDE_LOADING))
                }
                complete()
            })
        }
    }

    fun <T> executeOnlyResult(
        block: suspend CoroutineScope.() -> GenericResponse<T>,
        error: (r: ResponseException) -> Unit,
        success: (t: T) -> Unit,
        complete: () -> Unit,
        showLoading: Boolean
    ): Job {
        if (showLoading) {
            actionLiveData.postValue(ContextAction(ContextAction.ACTION_SHOW_LOADING))
        }
        return executeMain {
            try {
                val ret = withContext(Dispatchers.IO) {
                    block()
                }
                success(ret.data)
                complete()
            } catch (e: ResponseException) {
                error(e)
            }
        }
    }

    private suspend fun handleException(
        block: suspend CoroutineScope.() -> Unit,
        error: suspend CoroutineScope.(r: ResponseException) -> Unit,
        complete: suspend CoroutineScope.() -> Unit,
    ) {
        coroutineScope {
            try {
                block()
                complete()
            } catch (r: ResponseException) {
                error(r)
            }
        }
    }
}